import java.io.FileWriter;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.LinkedList;
import java.util.Map;
import java.util.HashMap;


public class CodeGeneration {
	private FileWriter outputFile = null;
	private Node rootNode = null;
	private Scope rootScope = null;
	
	// Serve para se poder dar os nomes das labels usadas nos ifs
	private int encounteredIfs = 0;
	
	// Serve para se poder dar os nomes das labels usadas nos whiles
	private int encounteredWhiles = 0;

	public CodeGeneration(Node rootNode) throws IOException {
		this.rootNode = rootNode;
		rootScope = new Scope(null, rootNode);
		
		try {
			outputFile = new FileWriter(getOutputFileName());
			outputFile.write(createStartTemplate());
			// outputFile.write(createFields());
			outputFile.write(createFunctions());
			outputFile.close();
		}
		catch (IOException e) {
			throw e;
		}
	}

	private String getOutputFileName() {
		return getModuleName().toLowerCase() + ".j";
	}
	
	private String getModuleName() {
		for (Node k : rootNode.jjtGetChildren()) {
			if (k.toString().equals("IDMODULE")) {
				SimpleNode SNode = (SimpleNode)k;
				return SNode.val;
			}
		}
		return null;
	}

	private String createStartTemplate() {
		return ".class public " + getModuleName() +"\n" +
			".super java/lang/Object\n" +
			"\n" +
			createFields() + 
			";\n" +
			"; standard initializer\n" +
			".method public <init>()V\n" +
			"    aload_0\n" +
			"\n" +
			"    invokenonvirtual java/lang/Object/<init>()V\n" +
			"    return\n" +
			"\n" +
			".end method\n";
	}
	
	private String createFields() {
		String outputString = "";
		
		for (Node k : rootNode.jjtGetChildren()) {
			if (k.toString().equals("Declaration")) {
				String variableName = null;
				String sign = null;

				/* So uma destas variaveis e que pode ser nao nula.
				 * Isto e, se a variavel (de YAL) for do tipo "int" e
				 * inicializada com um valor, "value" tem esse valor e
				 * "arraySize" e null.
				 *
				 * Por outro lado, se a variavel (de YAL) for do tipo
				 * "int[]" e inicializada com um valor, "arraySize"
				 * tem esse valor e "value" e null.
				 */ 
				String value = null;

				// Nota: Se "arraySize" != null, entao "sign" tem de
				// ser "null"
				String arraySize = null;
				boolean isArrayField = false;
				
				for (Node childNode : k.jjtGetChildren()) {
					if (childNode.toString().equals("IDScalarElement")) {
						SimpleNode childNodeSN = (SimpleNode)childNode;
						variableName = childNodeSN.val;
					}
					else if (childNode.toString().equals("Sign")) {
						SimpleNode childNodeSN = (SimpleNode)childNode;
						sign = childNodeSN.val;
					}
					else if (childNode.toString().equals("Integer")) {
						SimpleNode childNodeSN = (SimpleNode)childNode;
						value = childNodeSN.val;
					}
					else if (childNode.toString().equals("ArraySizeInteger")) {
						SimpleNode childNodeSN = (SimpleNode)childNode;
						arraySize = childNodeSN.val;
						isArrayField = true;
					}
				}

				System.out.println("variableName: " + variableName);
				System.out.println("Sign: " + sign);
				System.out.println("Value: " + value);
				System.out.println("arraySize: " + arraySize);

				if (!isArrayField && sign != null) {
					outputString += ".field static " + variableName + " I = " + sign + value;
				}
				else if (!isArrayField && arraySize == null && sign == null) {
					outputString += ".field static " + variableName + " I = " + value + "\n";
				}
				else if (isArrayField && arraySize != null) {
					outputString += ".field static " + variableName + " [I" + "\n";

					outputString += "; we need this to create the array " + variableName + " (with " + arraySize + "\n";
					outputString += "; elements)" + "\n";
					outputString += ".method static public <clinit>()V" + "\n";
					outputString += "   .limit stack 2" + "\n";
					outputString += "   .limit locals 0" + "\n";
					outputString += "   ; " + variableName + "  = new int[" + arraySize + "];" + "\n";
					outputString += "   bipush " + arraySize + "\n";
					outputString += "   newarray int" + "\n";
					outputString += "   putstatic " + getModuleName() + "/" + variableName + " [I" + "\n";
					outputString += "   return" + "\n";
					outputString += ".end method" + "\n";
				}
				else if (isArrayField && arraySize == null) {
					outputString += ".field static " + variableName + " [I" + "\n";
				}
				

				outputString += "\n";
			}
		}

		System.out.println(outputString);
		
		return outputString;
	}

	private Map<String, Integer> getVariablesInFunction(Function function) {
		int variablesInStack = 0;
		Map<String, Integer> variablesInFunction = null;
		
		// Primeiro, adicionar os argumentos da funcao
		if (function.getArguments() != null) {
			variablesInFunction = new HashMap<String, Integer>();
			
			for (Map.Entry<String, String> argument : function.getArguments().entrySet()) {
				variablesInFunction.put(argument.getKey(), variablesInStack);
				variablesInStack++;
			}
		}
		
		// Segundo, adicionar a variavel de retorno
		if (function.getReturnName() != null) {
			if (variablesInFunction == null) {
				variablesInFunction = new HashMap<String, Integer>();
			}
			
			variablesInFunction.put(function.getReturnName(), variablesInStack);
			variablesInStack++;
		}
		
		// Terceiro, adicionar as variaveis que aparecem do lado esquerdo dos assigns desta funcao
		Node functionBodyNode = getFunctionBodyNode(function.getFunctionName());
		
		for (Node leftAssignNode : getNodesByName(functionBodyNode, "LeftAssignSide")) {
			if (variablesInFunction == null) {
				variablesInFunction = new HashMap<String, Integer>();
			}
			
			/*
			 * Esta lista so pode ter tamanho 0 (o que significa que o lado
			 * esquerdo corresponde a uma variavel do tipo "int[]") ou 1 (o que
			 * significa que o lado esquerdo corresponde a uma variavel do tipo
			 * "int")
			 */
			List<Node> scalarNodes = getNodesByName(leftAssignNode, "IDScalarAccess");
			
			if (scalarNodes.size() != 0) {
				SimpleNode scalarNode = (SimpleNode) scalarNodes.get(0);
				
				variablesInFunction.put(scalarNode.val, variablesInStack);
				variablesInStack++;
			}
		}
		
		// TODO: E as variaveis globais?
		
		return variablesInFunction;
	}
	
	private String createFunctionPrototypeInJasminFormat(String functionName) {
		// Obter a funcao que tem o nome passado no argumento "functionName"
		Function functionToParse = null;
		
		for (Function function : rootScope.getFunctionsInScope()) {
			if (function.getFunctionName().equals(functionName)) {
				functionToParse = function;
			}
		}
		
		if (functionToParse == null) {
			return null;
		} else {
			return createFunctionPrototypeInJasminFormat(functionToParse);
		}		
	}
	
	private String createFunctionPrototypeInJasminFormat(Function function) {
		/* No caso do main, este deve ser sempre:
		 * .method public static main([Ljava/lang/String;)V
		 */
		if (function.getFunctionName().equals("main")) {
			return "main([Ljava/lang/String;)V";
		} 
		
		String outputString = function.getFunctionName() + "(";
		
		// Escrever paramatros da funcao em formato Jasmin
		if (function.getArguments() != null) {
			for (Map.Entry<String, String> argument : function.getArguments().entrySet()) {
				if (argument.getValue().equals("int"))
					outputString += "I";
				else // yal so tem 2 tipos de dados: "int" e "int[]"
					outputString += "[I";
			}
		}
		
		// Escrever retorno da funcao
		if (function.getReturnType() == null)
			outputString += ")V";
		else if (function.getReturnType().equals("int"))
			outputString += ")I";
		else
			outputString += ")[I";
		
		return outputString;
	}
	
	private String createFunctionHeader(Function function) {
		
		/* Gerar header da funcao.
		 * 
		 * Exemplo: No ficheiro yal:
		 * function a=f(b, c)
		 * 
		 * Fica:
		 * .method public static f(II)I
		 * .limit stack 2
		 * .limit locals 2
		 */
		String header = ".method public static " + createFunctionPrototypeInJasminFormat(function) +"\n";
		
		header += ".limit stack 100\n.limit locals 100";
		return header;
	}
	
	/**
	 * Faz parse de um conjunto generico de statments como os que sao encontrados nos nodes FunctionBody, IfBody, ElseBody e WhileBody.
	 * 
	 * @param node Um Node do tipo FunctionBody ou IfBody ou ElseBody ou WhileBody
	 * @param variablesInFunction um Map que mapeia o nome de uma variavel a uma posicao da stack
	 * @return Uma lista de instrucoes Jasmin
	 */
	private List<String> parseGenericStatment(Node node, Map<String, Integer> variablesInFunction) {
		List<String> jasminInstructions = new LinkedList<String>();

		for (Node childNode : node.jjtGetChildren()) {

			if (childNode.toString().equals("Assign")) {
				for (String instruction : parseAssignNode(childNode, variablesInFunction)) {
					jasminInstructions.add(instruction);
					System.out.println(instruction);
				}
			} else if (childNode.toString().equals("Call")) {
				for (String instruction : parseCallNode(childNode, variablesInFunction)) {
					jasminInstructions.add(instruction);
					System.out.println(instruction);
				}
			} else if (childNode.toString().equals("If")) {
				for (String instruction : parseIfNode(childNode, variablesInFunction)) {
					jasminInstructions.add(instruction);
					System.out.println(instruction);
				}
			} else if (childNode.toString().equals("While")) {
				for (String instruction : parseWhileNode(childNode, variablesInFunction)) {
					jasminInstructions.add(instruction);
					System.out.println(instruction);
				}
			}
		}
		
		return jasminInstructions;
	}

	private List<String> parseAssignNode(Node node, Map<String, Integer> variablesInFunction) {
		List<String> jasminInstructions = new LinkedList<String>();
		
		// Processar primeiro o token RightAssignSide
		for (Node childNode : node.jjtGetChildren()) {
			if (childNode.toString().equals("RightAssignSide")) {
				for (Node grandChildNode : childNode.jjtGetChildren()) {
					if (grandChildNode.toString().equals("OP_Aritmetico")) {
						jasminInstructions.addAll(parseOP_AritmeticoNode(grandChildNode, variablesInFunction));
					} else if (grandChildNode.toString().equals("Call")) {
						jasminInstructions.addAll(parseCallNode(grandChildNode, variablesInFunction));
					} else if (grandChildNode.toString().equals("OP_AddSub")) {
						jasminInstructions.addAll(parseOP_AddSubNode(grandChildNode, variablesInFunction));
					} else if (grandChildNode.toString().equals("Termo_Integer")) {
						/*
						 * Devido a um erro na construcao da arvore, quando
						 * aparece um token "OP_Aritmetico" ou "OP_AddSub", os
						 * nos do tipo "Termo_Integer" aparecem como seus irmaos
						 * e nao como seus filhos.
						 * 
						 * Por isso so podemos processar este Node se nenhum dos
						 * seus irmaos for um Node do tipo "OP_Aritmetico" ou
						 * "OP_AddSub".
						 */
						
						boolean shouldBeParsed = true;
						
						for (Node siblingNode : grandChildNode.jjtGetParent().jjtGetChildren()) {
							if (siblingNode.toString().equals("OP_Aritmetico")
									|| siblingNode.toString().equals("OP_AddSub")) {
								shouldBeParsed = false;
							}
						}

						if (shouldBeParsed) {
							jasminInstructions.addAll(parseTermo_IntegerNode(grandChildNode, variablesInFunction));
						}
					} else if (grandChildNode.toString().equals("ScalarAccess")) {
						/*
						 * Devido a um erro na construcao da arvore, quando
						 * aparece um token "OP_Aritmetico" ou "OP_AddSub", os
						 * nos do tipo "ScalarAccess" aparecem como seus irmaos
						 * e nao como seus filhos.
						 * 
						 * Por isso so podemos processar este Node se nenhum dos
						 * seus irmaos for um Node do tipo "OP_Aritmetico" ou
						 * "OP_AddSub".
						 */
						
						boolean shouldBeParsed = true;
						
						for (Node siblingNode : grandChildNode.jjtGetParent().jjtGetChildren()) {
							if (siblingNode.toString().equals("OP_Aritmetico")
									|| siblingNode.toString().equals("OP_AddSub")) {
								shouldBeParsed = false;
							}
						}
						
						if (shouldBeParsed) {		
							for (Node greatGrandChildNode : grandChildNode.jjtGetChildren()) {
								if (greatGrandChildNode.toString().equals("IDScalarAccess")) {
									SimpleNode greatGrandChildNodeSN = (SimpleNode) greatGrandChildNode;
								
									jasminInstructions.add("iload " + variablesInFunction.get(greatGrandChildNodeSN.val) + " ; variavel " + greatGrandChildNodeSN.val);
								}
							}
						}
					}
				}
			}
		}
		
		// Processar o token LeftAssignSide
		for (Node childNode : node.jjtGetChildren()) {
			if (childNode.toString().equals("LeftAssignSide")) {
				for (Node grandChildNode : childNode.jjtGetChildren()) {
					if (grandChildNode.toString().equals("ScalarAccess")) {
						for (Node greatGrandChildNode : grandChildNode.jjtGetChildren()) {
							if (greatGrandChildNode.toString().equals("IDScalarAccess")) {
								SimpleNode greatGrandChildNodeSN = (SimpleNode) greatGrandChildNode;
								jasminInstructions.add("istore " + variablesInFunction.get(greatGrandChildNodeSN.val) + " ; variavel " + greatGrandChildNodeSN.val);
							}
						}
					}
				}
			}
		}
		
		return jasminInstructions;
	}
	
	private List<String> parseOP_AritmeticoNode(Node node, Map<String, Integer> variablesInFunction) {
		List<String> jasminInstructions = new LinkedList<String>();
		
		// Isto pode ser util para quando se utilizar os operadores << ou >>
		int leftScalarAccessVariablePosition = -1;
		
		for (Node siblingNode : node.jjtGetParent().jjtGetChildren()) {
			if (siblingNode.toString().equals("ScalarAccess") 
					&& siblingNode.jjtGetChild(0).toString().equals("IDScalarAccess")) {
				SimpleNode scalarNode = (SimpleNode) siblingNode.jjtGetChild(0);

				// TODO: Optimizar isto para poder usar iload_1 em vez de iload 1
				jasminInstructions.add("iload " + variablesInFunction.get(scalarNode.val) + " ; variavel " + scalarNode.val);
			} else if (siblingNode.toString().equals("Termo_Integer")) {
				SimpleNode termo_IntegerNode = (SimpleNode) siblingNode;
				jasminInstructions.addAll(parseTermo_IntegerNode(termo_IntegerNode, variablesInFunction));
			}

		}
		
		SimpleNode nodeSN = (SimpleNode) node;
		
		if (nodeSN.val.equals("*")) {
			jasminInstructions.add("imul");
		} else if (nodeSN.val.equals("/")) {
			jasminInstructions.add("idiv");
		} 
		
		return jasminInstructions;
	}
	
	private List<String> parseOP_AddSubNode(Node node, Map<String, Integer> variablesInFunction) {
		List<String> jasminInstructions = new LinkedList<String>();;
		
		for (Node siblingNode : node.jjtGetParent().jjtGetChildren()) {
			if (siblingNode.toString().equals("ScalarAccess")
					&& siblingNode.jjtGetChild(0).toString().equals("IDScalarAccess")) {
				SimpleNode scalarNode = (SimpleNode) siblingNode.jjtGetChild(0);
				
				// TODO: Optimizar isto para poder usar iload_1 em vez de iload 1
				jasminInstructions.add("iload " + variablesInFunction.get(scalarNode.val) + " ; variavel " + scalarNode.val);
			} else if (siblingNode.toString().equals("Termo_Integer")) {
				SimpleNode termo_IntegerNode = (SimpleNode) siblingNode;
				jasminInstructions.addAll(parseTermo_IntegerNode(termo_IntegerNode, variablesInFunction));
			}
		}
		
		SimpleNode nodeSN = (SimpleNode) node;
		
		if (nodeSN.val.equals("+")) {
			jasminInstructions.add("iadd");
		} else if (nodeSN.val.equals("-")) {
			jasminInstructions.add("idiv");
		}
					
		return jasminInstructions;
	}
	
	private List<String> parseTermo_IntegerNode(Node node, Map<String, Integer> variablesInFunction) {
		List<String> jasminInstructions = new LinkedList<String>();

		/*
		 * Verificar se o termo e negativo. Isso e visto verificando a
		 * existencia de um no irmao com o token Sinal_Numero com o valor "-"
		 */
		boolean isNegativeNumber = false;
		for (Node siblingNode : node.jjtGetParent().jjtGetChildren()) {
			if (siblingNode.toString().equals("Sinal_Numero")) {
				SimpleNode siblingNodeSN = (SimpleNode) siblingNode;
				if (siblingNodeSN.val.equals("-")) {
					isNegativeNumber = true;
				}
			}
		}
		
		SimpleNode nodeSN = (SimpleNode) node;
		
		if (isNegativeNumber) {
			jasminInstructions.add("ldc -" + nodeSN.val);
		} else {
			jasminInstructions.add("ldc " + nodeSN.val);
		}
			
		return jasminInstructions;
	}
	
	private List<String> parseCallNode(Node node, Map<String, Integer> variablesInFunction) {
		List<String> jasminInstructions = new LinkedList<String>();

		// Primeiro, obter o valor dos token NomeClasse e NomeMetodo e ArgumentList
		String nomeClasse = null;
		String nomeMetodo = null;
		Node ArgumentListNode = null;
		
		for (Node childNode : node.jjtGetChildren()) {
			if (childNode.toString().equals("NomeClasse")) {
				SimpleNode childNodeSN = (SimpleNode) childNode;
				nomeClasse = childNodeSN.val;
			} else if (childNode.toString().equals("NomeMetodo")) {
				SimpleNode childNodeSN = (SimpleNode) childNode;
				nomeMetodo = childNodeSN.val;
			} else if (childNode.toString().equals("ArgumentList")) {
				ArgumentListNode = childNode;
			}
		}

		/*
		 * Colocar os valores dos argumentos na stack antes de chamar a funcao.
		 * 
		 * Podem aparecer tres tipos diferentes de tokens no ArgumentListNode:
		 * 
		 * 1. INTArgument - Um argumento que e um inteiro. Deve ser usada a
		 * instrucao "iconst" ou "ldc" para o meter na stack;
		 * 
		 * 2. IDArgument - Um argumento que e o identificador de uma variavel
		 * existente em "variablesInFunction". Deve ser usada a instrucao
		 * "iload" para o meter na stack;
		 * 
		 * 3. STRArgument - Um argumento que e uma string. Deve ser usada a
		 * instrucao "ldc" para o meter na stack.
		 * 
		 * (/!\ IGNORAR /!\) Nota: Os argumentos devem ser colocados na stack pela ordem inversa
		 * em que aparecem. Por isso e usada uma lista temporaria de instrucoes
		 * que depois e invertida.
		 */
		List<String> temporaryInstructions = new LinkedList<String>();
		
		for (Node childNode : ArgumentListNode.jjtGetChildren()) {
			
			SimpleNode childNodeSN = (SimpleNode) childNode;
			
			/*if (childNode.toString().equals("INTArgument")) {
				temporaryInstructions.add("ldc " + childNodeSN.val);
			} else if (childNode.toString().equals("IDArgument")) {
				temporaryInstructions.add("iload " + variablesInFunction.get(childNodeSN.val) + " ; variavel " + childNodeSN.val); 
			} else if (childNode.toString().equals("STRArgument")) {
				temporaryInstructions.add("ldc " + childNodeSN.val);
			}*/
			if (childNode.toString().equals("INTArgument")) {
				jasminInstructions.add("ldc " + childNodeSN.val);
			} else if (childNode.toString().equals("IDArgument")) {
				jasminInstructions.add("iload " + variablesInFunction.get(childNodeSN.val) + " ; variavel " + childNodeSN.val); 
			} else if (childNode.toString().equals("STRArgument")) {
				jasminInstructions.add("ldc " + childNodeSN.val);
			}
		}
		
		/*
		for (int k = temporaryInstructions.size()-1; k >= 0; k--) {
			jasminInstructions.add(temporaryInstructions.get(k));
		}*/
		
		
		
		/*
		 * Se nomeMetodo for "null", entao a funcao que esta a ser invocada esta
		 * neste modulo, caso constrario, esta a ser invocada neste modulo.
		 * 
		 * nomeClasse tera sempre um valor qualquer diferente de "null".
		 * 
		 * Exemplo:
		 * 
		 * nomeClasse = foo nomeMetodo = null
		 * 
		 * Corresponde a invocar a funcao "foo" deste modulo
		 * 
		 * nomeClasse = foo nomeMetodo = bar
		 * 
		 * Coresponde a invocar a funcao "bar" do modulo "foo"
		 */
		if (nomeMetodo == null) {
			jasminInstructions.add("invokestatic " + getModuleName() + "/" + createFunctionPrototypeInJasminFormat(nomeClasse));
		} else {			
			//jasminInstructions.add("invokestatic " + nomeClasse + "/" + createFunctionPrototypeInJasminFormat(nomeMetodo));
			
			String instruction = "invokestatic " + nomeClasse + "/" + nomeMetodo +"(";

			/*
			 * Tem que se assumir que os tipos dos argumentos da funcao estao
			 * correctos e como tal podemos usar-los para criar o prototipo para
			 * a instrucao "invokestatic".
			 * 
			 * (/!\ IGNORAR! /!\) Nota: Tal como aconteceu com o processamento do Node
			 * ArgumentListNode, os argumentos devem ser descritos pela ordem
			 * inversa com que aparecem.
			 */
			List<String> temporaryPrototype = new LinkedList<String>();
			
			for (Node childNode : ArgumentListNode.jjtGetChildren()) {
				if (childNode.toString().equals("INTArgument")) {
					//temporaryPrototype.add("I");
					instruction += "I";
				} else if (childNode.toString().equals("IDArgument")) {
					// TODO: Assume-se que as variaveis sao do tipo "int"
					//temporaryPrototype.add("I");
					instruction += "I";
				} else if (childNode.toString().equals("STRArgument")) {
					//temporaryPrototype.add("Ljava/lang/String;");
					instruction += "Ljava/lang/String;";
				}
			}
			
			/*for (int k = temporaryPrototype.size()-1; k >= 0; k--) {
				instruction += temporaryPrototype.get(k);
			}*/
			
			instruction += ")";
			
			/*
			 * FIXME: Assume-se que se o Node pai deste Call for
			 * RightAssignSide, entao a variavel no LeftAssignSide e do tipo
			 * "int", caso contrario, assume-se que esta funcao externa devolve
			 * "void"
			 */
			if (node.jjtGetParent().toString().equals("RightAssignSide")) {
				instruction += "I";
			} else {
				instruction += "V";
			}
			
			jasminInstructions.add(instruction);			
		}
		
		return jasminInstructions;
	}
	
	private List<String> parseIfNode(Node node, Map<String, Integer> variablesInFunction) {
		// encounteredIfs e usado para numerar as labels, por exemplo: "if1cond", "else1", "if1end"		
		encounteredIfs++;

		/*
		 * E necessario guardar agora os valores das possiveis labels que podem
		 * existir porque se existirem outros ifs dentro deste, a variavel
		 * "encounteredIfs" modifica-se e depois ja nao e possivel fazer as
		 * labels todas.
		 * 
		 * A label para o corpo do else deve ser criada mesmo que nao exista
		 * nenhum else para este if.
		 */
		String conditionLabel = "if" + encounteredIfs + "cond";
		String ifEndLabel = "if" + encounteredIfs + "end";
		String elseLabel = "else" + encounteredIfs;
		
		List<String> jasminInstructions = new LinkedList<String>();
		
		jasminInstructions.add(conditionLabel + ":");
		
		// Encontrar o no da condicao
		Node ifConditionNode = null;
		
		for (Node childNode : node.jjtGetChildren()) {
			if (childNode.toString().equals("IfCondition")) {
				ifConditionNode = childNode;
			}
		}
		
		// NOTA: Tem de existir sempre um Node do tipo IfCondition, por isso nao
		// vale a pena verificar se ifConditionNode e null
		
		/*
		 * Antes de se chamar a funcao para criar o codigo para a condicao, e
		 * preciso saber para que label o codigo deve saltar se a condicao for
		 * falsa.
		 * 
		 * Ha duas opcoes:
		 * 
		 * 1. Se este if nao tiver um else que lhe corresponda, entao deve-se
		 * saltar para o if<numero>_end
		 * 
		 * 2. Caso contrario, deve saltar para a label else<numero>
		 */
		boolean hasElseBody = false;
		
		for (Node childNode : node.jjtGetChildren()) {
			if (childNode.toString().equals("ElseBody")) {
				hasElseBody = true;
			}
		}
		
		if (!hasElseBody) {
			jasminInstructions.addAll(parseConditionNode(ifConditionNode, variablesInFunction, ifEndLabel));
		} else {
			jasminInstructions.addAll(parseConditionNode(ifConditionNode, variablesInFunction, elseLabel));
		}

		/*
		 * Para processar o corpo do if:
		 * 
		 * 1. Encontrar o node IfBody
		 * 
		 * 2. Chamar parseGenericStatment no IfBody
		 * 
		 * 3. Se existir um else, adicionar a instrucao de salto para a label
		 * "if1end:" para saltar por cima do else
		 */
		for (Node childNode : node.jjtGetChildren()) {
			if (childNode.toString().equals("IfBody")) {
				jasminInstructions.addAll(parseGenericStatment(childNode, variablesInFunction));
				
				if (hasElseBody) {
					jasminInstructions.add("goto " + ifEndLabel);
				}
			}
		}
		
		/*
		 *  O corpo do else e processado de forma semelhante ao corpo do if:
		 *  
		 *  1. Adicionar ao jasminInstructions a label "else<numero>:"
		 *  
		 *  2. Encontrar o node ElseBody
		 *  
		 *  3. Chamar parseGenericStatment no ElseBody 
		 */
		if (hasElseBody) {
			jasminInstructions.add(elseLabel + ":");
			
			for (Node childNode : node.jjtGetChildren()) {
				if (childNode.toString().equals("ElseBody")) {
					jasminInstructions.addAll(parseGenericStatment(childNode, variablesInFunction));
				}
			}
		}
		
		// Adicionar a label de fim do if
		
		jasminInstructions.add(ifEndLabel + ":");
		
		return jasminInstructions;
	}
	
	private List<String> parseWhileNode(Node node, Map<String, Integer> variablesInFunction) {
		// encounteredWhiles e usado para numerar as labels, por exemplo: "while1cond", "while1end"		
		encounteredWhiles++;

		/*
		 * E necessario guardar agora os valores das possiveis labels que podem
		 * existir porque se existirem outros whiles dentro deste, a variavel
		 * "encounteredWhiles" modifica-se e depois ja nao e possivel fazer as
		 * labels todas.
		 */
		String conditionLabel = "while" + encounteredWhiles + "cond";
		String endLabel = "while" + encounteredWhiles + "end";
		
		List<String> jasminInstructions = new LinkedList<String>();
		
		jasminInstructions.add(conditionLabel + ":");
		
		// Encontrar o no da condicao
		Node whileConditionNode = null;
		
		for (Node childNode : node.jjtGetChildren()) {
			if (childNode.toString().equals("WhileCondition")) {
				whileConditionNode = childNode;
			}
		}
		
		// NOTA: Tem de existir sempre um Node do tipo WhileCondition, por isso nao
		// vale a pena verificar se whileConditionNode e null
		
		jasminInstructions.addAll(parseConditionNode(whileConditionNode, variablesInFunction, endLabel));

		/*
		 * Para processar o corpo do while:
		 * 
		 * 1. Encontrar o node WhileBody
		 * 
		 * 2. Chamar parseGenericStatment no WhileBody
		 * 
		 * 3. Adicionar a instrucao de salto paro inicio do loop, que e a label
		 * que esta guardada em conditionLabel.
		 */
		for (Node childNode : node.jjtGetChildren()) {
			if (childNode.toString().equals("WhileBody")) {
				jasminInstructions.addAll(parseGenericStatment(childNode, variablesInFunction));
				
				jasminInstructions.add("goto " + conditionLabel);
			}
		}

		// Adicionar a label de fim do while
		
		jasminInstructions.add(endLabel + ":");

		
		
		return jasminInstructions;
	}
	
	/**
	 * 
	 * @param node Node de ser do tipo IfCondition ou WhileCondition
	 * @param variablesInFunction
	 * @param labelToJump
	 * @return
	 */
	private List<String> parseConditionNode(Node node, Map<String, Integer> variablesInFunction, String labelToJump) {
		List<String> jasminInstructions = new LinkedList<String>();

		/*
		 * A condicao de salto deve ser a inversa do codigo Yal, porque indica
		 * quando se deve saltar para o corpo do else (ou para fora do if se
		 * este nao tiver um else associado).
		 * 
		 * Por exemplo, se no ficheiro Yal aparecer a condicao:
		 * 
		 * if (a < b) {
		 * 
		 * }
		 * 
		 * 
		 * As instrucoes jasmin devem ser geradas de forma a so saltar para fora
		 * do if se a condicao "a >= b" for verdadeira.
		 */
		
		// Primeiro meter as variaveis na stack pela ordem da esquerda para a direita
		for (Node childNode : node.jjtGetChildren()) {
			if (childNode.toString().equals("ScalarAccess")) {
				for (Node grandChildNode : childNode.jjtGetChildren()) {
					if (grandChildNode.toString().equals("IDScalarAccess")) {
						SimpleNode grandChildNodeSN = (SimpleNode) grandChildNode;
						
						jasminInstructions.add("iload " + variablesInFunction.get(grandChildNodeSN.val) + " ; variavel " + grandChildNodeSN.val);
					}
				}
			}
		}
		
		// Obter a operacao de comparacao que existe no ficheiro Yal
		String relationOperator = null;
		
		for (Node childNode : node.jjtGetChildren()) {
			if (childNode.toString().equals("OperadorRelacao")) {
				SimpleNode childNodeSN = (SimpleNode) childNode;
				relationOperator = childNodeSN.val;
			}
		}
		
		// Existe sempre um OperadorRelacao, por isso nao e preciso verificar se relationOperator continua a ser null
		
		/*
		 * Fazer a conversao entre o operador Yal e o seu oposto em linguagem Jasmin.
		 * 
		 * As conversoes sao as seguintes:
		 * 
		 * | Operador Yal | Operador Yal oposto ao original | Instrucao Jasmin a usar |
		 * |--------------+---------------------------------+-------------------------|
		 * | >            | <=                              | if_icmple               |
		 * | <            | >=                              | if_icmpge               |
		 * | <=           | >                               | if_icmpgt               |
		 * | >=           | <                               | if_icmplt               |
		 * | ==           | !=                              | if_icmpne               |
		 * | !=           | ==                              | if_icmpeq               |
		 */
		if (relationOperator.equals(">")) {
			jasminInstructions.add("if_icmple " + labelToJump);
		} else if (relationOperator.equals("<")) {
			jasminInstructions.add("if_icmpge " + labelToJump);
		} else if (relationOperator.equals("<=")) {
			jasminInstructions.add("if_icmpgt " + labelToJump);
		} else if (relationOperator.equals(">=")) {
			jasminInstructions.add("if_icmplt " + labelToJump);
		} else if (relationOperator.equals("==")) {
			jasminInstructions.add("if_icmpne " + labelToJump);
		} else if (relationOperator.equals("!=")) {
			jasminInstructions.add("if_icmpeq " + labelToJump);
		}
				
		return jasminInstructions;
	}
	
	private String createFunctions() {
		String outputString = "";

		if (rootScope.getFunctionsInScope() != null) {
			for (Function function : rootScope.getFunctionsInScope()) {

				outputString += createFunctionHeader(function)+"\n";
				System.out.println(createFunctionHeader(function));
				
				Map<String, Integer> variablesInFunction = getVariablesInFunction(function);

				if (variablesInFunction != null) {
					
					Node functionBodyNode = getFunctionBodyNode(function.getFunctionName());

					List<String> jasminInstructions = parseGenericStatment(
							functionBodyNode, variablesInFunction);

					for (String instruction : jasminInstructions) {
						outputString += instruction + "\n";
					}
					
					if (function.getReturnType() == null) {
						outputString += "return\n";
						System.out.println("return");
					}
					else {
						outputString += "iload " + variablesInFunction.get(function.getReturnName()) + "\n";
						outputString += "ireturn\n";
						System.out.println("iload " + variablesInFunction.get(function.getReturnName()));
						System.out.println("ireturn");
					}
				}
				
				outputString += ".end method\n";
			}
		}
		return outputString;
	}

	private List<Node> getNodesByName(String nodeName) {
		return getNodesByName(rootNode, nodeName);
	}
	
	/** Encontrar nodes que estejam abaixo do node <i>node</i> e que tenham um dado nome <i>nodeName</i>.
	 * 
	 * @param node O node que vai ser analisado
	 * @param nodeName O nome dos Node que se quer encontrar
	 * @return Lista de Node que tem como nome o valor de <i>nodeName</i>
	 */
	private List<Node> getNodesByName(Node node, String nodeName) {
		if (node == null)
			return null;

		List<Node> totalNodesFound = null;
		
		if (node.jjtGetChildren() != null) {
			for (Node childNode : node.jjtGetChildren()) {
				if (childNode.toString().equals(nodeName)) {
					if (totalNodesFound == null)
						totalNodesFound = new LinkedList<Node>();
					
					totalNodesFound.add(childNode);
				}

				List<Node> nodesFound = getNodesByName(childNode, nodeName);

				if (nodesFound != null) {
					if (totalNodesFound == null)
						totalNodesFound = new LinkedList<Node>();
					
					totalNodesFound.addAll(nodesFound);
				}
			}
		}
		return totalNodesFound;
	}
	
	private Node getFunctionBodyNode(String functionName) {
		// Obter todos os Nodes que contem os nomes das funcoes definidas neste modulo
		List<Node> functionNodesWithReturn = getNodesByName("IDFunction_com_return");
		List<Node> functionNodesWithoutReturn = getNodesByName("IDFunction_sem_return");
		
		// uma lista com todos os Node de IDFunction (com ou sem returno)
		List<Node> functionNodes = null;
		
		if (functionNodesWithReturn == null && functionNodesWithoutReturn == null)
			return null;
		else if (functionNodesWithReturn == null && functionNodesWithoutReturn != null)
			functionNodes = functionNodesWithoutReturn;
		else if (functionNodesWithReturn != null && functionNodesWithoutReturn == null)
			functionNodes = functionNodesWithReturn;
		else { // Juntar as duas listas
			functionNodes = new LinkedList<Node>();
			
			functionNodes.addAll(functionNodesWithReturn);
			functionNodes.addAll(functionNodesWithoutReturn);
		}
		

		for (Node functionNode : functionNodes) {
			SimpleNode functionNodeSN = (SimpleNode) functionNode;

			if (functionNodeSN.val.equals(functionName)) {
				// Procurar pelo Node FunctionBody e devolver-lo
				for (Node siblingNode : functionNode.jjtGetParent().jjtGetChildren()) {
					if (siblingNode.toString().equals("FunctionBody")) {
						return siblingNode;
					}
				}
			}
		}

		return null;
	}
}
